<?php

class HasAndBelongsToManyAssociation extends Association
{

  public function loadData()
  {
    if (! $this->dataLoaded && ! $this->owner->has($this->name)) {
      $model = $this->getModel();

      $id = $this->owner->id;

      $at = $this->getAssociationTable();
      $pk = $this->getPrimaryKey();
      $fk = $this->getForeignKey();
      $afk = $this->getAssociationForeignKey();
      $apk = $this->getAssociationPrimaryKey();

      if (isset($model['fields'])) {
        $fields = $model['fields'];

        $q = DB::getInstance()->query()->select("{$at}.{$afk}");

        foreach ($fields as &$field) {
          $field = new FieldModel($field);

          $q->select("{$at}." . $field->getName());
        }

        $q->from($at)->where("{$at}.{$apk} = :{$pk}");

        $data = $q->execute(array($pk => $id))->fetchAll();

        if (! empty($data)) {
          $ids = array();

          foreach ($data as $row) {
            $ids[] = $row[$afk];
          }

          $ids = implode(', ', $ids);
        }
      }
      else {
        $q = DB::getInstance()->query()->select("{$at}.{$afk}")->from($at)->where("{$at}.{$apk} = :{$pk}");

        $data = $q->execute(array($pk => $id))->fetchCol();

        if (! empty($data)) {
          $ids = implode(', ', $data);
        }
      }

      $targetName = Domain::getObject($this->target)->getName();

      if (! empty($data)) {
        $r = new CustomRepository($this->target);

        $objs = $r->findAll(array(
          'where' => "{$fk} IN ($ids)"
        ));

        if (isset($fields)) {
          foreach ($objs as $k => &$obj) {
            $_obj = array();

            foreach ($fields as $name => &$field) {
              $_obj[$name] = $data[$k][$field->getName()];
            }

            $obj = array_merge($_obj, array($targetName => $obj));
          }
        }
        else {
          foreach ($objs as &$obj) {
            $obj = array($targetName => $obj);
          }
        }
      }
      else {
        $objs = array();
      }

      $this->owner->{$this->name} = $objs;

      $this->dataLoaded = true;
    }
  }

  public function save()
  {
    $model = $this->getModel();

    $id = $this->owner->id;

    $at = $this->getAssociationTable();
    $pk = $this->getPrimaryKey();
    $fk = $this->getForeignKey();
    $afk = $this->getAssociationForeignKey();
    $apk = $this->getAssociationPrimaryKey();

    $q = DB::getInstance()->query()->select("{$at}.{$afk}")->from($at)->where("{$at}.{$apk} = :{$pk}");

    $old = $q->execute(array($model['primaryKey'] => $id))->fetchCol();

    $new = array();

    $targetName = Domain::getObject($model['target'])->getName();

    $r = new CustomRepository($model['target']);

    foreach ($this->owner->{$this->name} as $i => &$row) {
      $r->save($row[$targetName]);

      $new[] = $row[$targetName]->id;
    }

    $del = array_diff($old, $new);

    if (! empty($del)) {
      $del = implode(', ', $del);

      $q = DB::getInstance()->delete($at, "{$at}.{$apk} = :{$pk} AND {$at}.{$afk} IN ($del)")->execute(array($pk => $id));
    }

    $ins = array_diff($new, $old);

    if (isset($model['fields'])) {
      $fields = $model['fields'];

      $objs = array();
      foreach ($this->owner->{$this->name} as $obj) {
        $objs[$obj[$targetName]->id] = $obj;
      }

      foreach ($fields as &$field) {
        $field = new FieldModel($field);
      }

      foreach ($ins as $_id) {
        $row = array(
          $apk => $id,
          $afk => $_id
        );

        foreach ($fields as $name => $field) {
          $row[$field->getName()] = $objs[$_id][$name];
        }

        $q = DB::getInstance()->insert($at, $row)->execute($row);
      }
    }
    else {
      foreach ($ins as $row) {
        $row = array(
          $apk => $id,
          $afk => $row
        );

        $q = DB::getInstance()->insert($at, $row)->execute($row);
      }
    }
  }

  public function getAssociationTable()
  {
    if (! isset($this->model['associationTable'])) {
      $tables = array(
        Domain::getObject($this->source)->getTable(),
        Domain::getObject($this->target)->getTable(),
      );

      sort($tables);

      $this->model['associationTable'] = implode('_', $tables);
    }

    return $this->model['associationTable'];
  }

  public function getAssociationPrimaryKey()
  {
    if (! isset($this->model['associationPrimaryKey'])) {
      $this->model['associationPrimaryKey'] = $this->getPrimaryKey();
    }

    return $this->model['associationPrimaryKey'];
  }

  public function getAssociationForeignKey()
  {
    if (! isset($this->model['associationForeignKey'])) {
      $this->model['associationForeignKey'] = $this->getForeignKey();
    }

    return $this->model['associationForeignKey'];
  }

}
